Imports System
Imports System.Collections
Imports System.Collections.Specialized
Imports System.Diagnostics
Imports System.Globalization
Imports System.Security.Permissions
Imports System.Web
Imports System.Web.UI
Imports System.Web.Util
'TODO: COMPLETE THIS PROVIDER
Public MustInherit Class DBSiteMapProvider
    Inherits SiteMapProvider

    Friend AppDomainAppVirtualPathWithTrailingSlash As String = VirtualPathUtility.AppendTrailingSlash(HttpRuntime.AppDomainAppVirtualPath)
    Friend Shared Empty As SiteMapNodeCollection = New SiteMapNodeCollection
    Friend _lock As Object = New Object

    ' Table maps nodes to their child node collections
    Private _childNodeCollectionTable As Hashtable
    ' Case sensitive table that maps key to sitemap node.
    Private _keyTable As Hashtable
    ' Table maps nodes to their parent nodes
    Private _parentNodeTable As Hashtable
    ' Case insensitive table that maps url to sitemap node.
    Private _urlTable As Hashtable

    Friend ReadOnly Property ChildNodeCollectionTable() As IDictionary
        Get
            If (_childNodeCollectionTable Is Nothing) Then
                If (_childNodeCollectionTable Is Nothing) Then
                    _childNodeCollectionTable = New Hashtable
                End If
            End If
            Return _childNodeCollectionTable
        End Get
    End Property

    Friend ReadOnly Property KeyTable() As IDictionary
        Get
            If (_keyTable Is Nothing) Then
                If (_keyTable Is Nothing) Then
                    _keyTable = New Hashtable
                End If
            End If
            Return _keyTable
        End Get
    End Property

    Friend ReadOnly Property ParentNodeTable() As IDictionary
        Get
            If (_parentNodeTable Is Nothing) Then
                If (_parentNodeTable Is Nothing) Then
                    _parentNodeTable = New Hashtable
                End If
            End If
            Return _parentNodeTable
        End Get
    End Property

    Friend ReadOnly Property UrlTable() As IDictionary
        Get
            If (_urlTable Is Nothing) Then
                If (_urlTable Is Nothing) Then
                    _urlTable = New Hashtable(StringComparer.OrdinalIgnoreCase)
                End If
            End If
            Return _urlTable
        End Get
    End Property

    ''' <devdoc>
    '''    <para>Add single node to provider tree and sets the parent-child relation.</para>
    ''' </devdoc>
    Protected Overrides Sub AddNode(ByVal node As SiteMapNode, ByVal parentNode As SiteMapNode)
        If (node Is Nothing) Then
            Throw New ArgumentNullException("node")
        End If
        Dim validUrl As Boolean = False
        Dim url As String = node.Url
        If Not String.IsNullOrEmpty(url) Then
            If (Not (AppDomainAppVirtualPathWithTrailingSlash) Is Nothing) Then
                If Not IsAbsolutePhysicalPath(url) Then
                    url = VirtualPathUtility.Combine(AppDomainAppVirtualPathWithTrailingSlash, url)
                    ' Normalize url
                    url = VirtualPathUtility.ToAbsolute(url)
                End If
                If (Not (UrlTable(url)) Is Nothing) Then
                    Throw New InvalidOperationException("DBSiteMapProvider Multiple Nodes With Identical Url, " & url)
                End If
            End If
            validUrl = True
        End If
        Dim key As String = node.Key
        Debug.Assert((Not (key) Is Nothing))
        If KeyTable.Contains(key) Then
            Throw New InvalidOperationException("DBSiteMapProvider Multiple Nodes With Identical Key, " & key)
        End If
        KeyTable(key) = node
        If validUrl Then
            UrlTable(url) = node
        End If
        If (Not (parentNode) Is Nothing) Then
            ParentNodeTable(node) = parentNode
            If (ChildNodeCollectionTable(parentNode) Is Nothing) Then
                ChildNodeCollectionTable(parentNode) = New SiteMapNodeCollection
            End If
            CType(ChildNodeCollectionTable(parentNode), SiteMapNodeCollection).Add(node)
        End If
    End Sub

    Public MustOverride Function BuildSiteMap() As SiteMapNode

    ' Clear tables
    Protected Overridable Sub Clear()
        If (Not (_childNodeCollectionTable) Is Nothing) Then
            _childNodeCollectionTable.Clear()
        End If
        If (Not (_urlTable) Is Nothing) Then
            _urlTable.Clear()
        End If
        If (Not (_parentNodeTable) Is Nothing) Then
            _parentNodeTable.Clear()
        End If
        If (Not (_keyTable) Is Nothing) Then
            _keyTable.Clear()
        End If
    End Sub

    ' Find sitemapnode in current provider
    Public Overrides Function FindSiteMapNodeFromKey(ByVal key As String) As SiteMapNode
        Dim result As SiteMapNode = MyBase.FindSiteMapNodeFromKey(key)
        If (result Is Nothing) Then
            result = CType(KeyTable(key), SiteMapNode)
        End If
        Return ReturnNodeIfAccessible(result)
    End Function

    ' Find sitemapnode in current provider
    Public Overrides Function FindSiteMapNode(ByVal rawUrl As String) As SiteMapNode
        If (rawUrl Is Nothing) Then
            Throw New ArgumentNullException("rawUrl")
        End If
        rawUrl = rawUrl.Trim
        If (rawUrl.Length = 0) Then
            Return Nothing
        End If
        Dim virtualPath As String = rawUrl
        Dim qs As Integer = rawUrl.IndexOf(Microsoft.VisualBasic.ChrW(63))
        If (qs <> -1) Then
            virtualPath = rawUrl.Substring(0, qs)
        End If
        ' Make sure it is an app absolute url
        If VirtualPathUtility.IsAppRelative(virtualPath) Then
            virtualPath = VirtualPathUtility.ToAbsolute(virtualPath)
        End If
        If (qs <> -1) Then
            virtualPath = (virtualPath + rawUrl.Substring(qs))
        End If
        BuildSiteMap()
        Return ReturnNodeIfAccessible(CType(UrlTable(virtualPath), SiteMapNode))
    End Function

    ' Return readonly child node collection
    Public Overrides Function GetChildNodes(ByVal node As SiteMapNode) As SiteMapNodeCollection
        If (node Is Nothing) Then
            Throw New ArgumentNullException("node")
        End If
        BuildSiteMap()
        Dim collection As SiteMapNodeCollection = CType(ChildNodeCollectionTable(node), SiteMapNodeCollection)
        If (collection Is Nothing) Then
            Dim childNodeFromKey As SiteMapNode = CType(KeyTable(node.Key), SiteMapNode)
            If (Not (childNodeFromKey) Is Nothing) Then
                collection = CType(ChildNodeCollectionTable(childNodeFromKey), SiteMapNodeCollection)
            End If
        End If
        If (Not (collection) Is Nothing) Then
            If Not SecurityTrimmingEnabled Then
                Return SiteMapNodeCollection.ReadOnly(collection)
            End If
            Dim context As HttpContext = HttpContext.Current
            Dim trimmedCollection As SiteMapNodeCollection = New SiteMapNodeCollection(collection.Count)
            For Each subNode As SiteMapNode In collection
                If subNode.IsAccessibleToUser(context) Then
                    trimmedCollection.Add(subNode)
                End If
            Next
            Return SiteMapNodeCollection.ReadOnly(trimmedCollection)
        End If
        Return Nothing
    End Function

    Public Overrides Function GetParentNode(ByVal node As SiteMapNode) As SiteMapNode
        If (node Is Nothing) Then
            Throw New ArgumentNullException("node")
        End If
        BuildSiteMap()
        Dim parent As SiteMapNode = CType(ParentNodeTable(node), SiteMapNode)
        If (parent Is Nothing) Then
            ' Try to find the original node in the table using the key
            Dim fallbackNode As SiteMapNode = CType(KeyTable(node.Key), SiteMapNode)
            If (Not (fallbackNode) Is Nothing) Then
                parent = CType(ParentNodeTable(fallbackNode), SiteMapNode)
            End If
        End If
        ' Try the parent providers.
        If ((parent Is Nothing) _
                    AndAlso (Not (ParentProvider) Is Nothing)) Then
            parent = ParentProvider.GetParentNode(node)
        End If
        Return ReturnNodeIfAccessible(parent)
    End Function

    Protected Overrides Sub RemoveNode(ByVal node As SiteMapNode)
        If (node Is Nothing) Then
            Throw New ArgumentNullException("node")
        End If
        Dim oldParent As SiteMapNode = CType(ParentNodeTable(node), SiteMapNode)
        If ParentNodeTable.Contains(node) Then
            ParentNodeTable.Remove(node)
        End If
        If (Not (oldParent) Is Nothing) Then
            Dim collection As SiteMapNodeCollection = CType(ChildNodeCollectionTable(oldParent), SiteMapNodeCollection)
            If ((Not (collection) Is Nothing) _
                        AndAlso collection.Contains(node)) Then
                collection.Remove(node)
            End If
        End If
        Dim url As String = node.Url
        If ((Not (url) Is Nothing) _
                    AndAlso ((url.Length > 0) _
                    AndAlso UrlTable.Contains(url))) Then
            UrlTable.Remove(url)
        End If
        Dim key As String = node.Key
        If KeyTable.Contains(key) Then
            KeyTable.Remove(key)
        End If
    End Sub

    Friend Function ReturnNodeIfAccessible(ByVal node As SiteMapNode) As SiteMapNode
        If ((Not (node) Is Nothing) _
                    AndAlso node.IsAccessibleToUser(HttpContext.Current)) Then
            Return node
        End If
        Return Nothing
    End Function

    Friend Shared Function IsAbsolutePhysicalPath(ByVal path As String) As Boolean
        If ((path Is Nothing) _
                    OrElse (path.Length < 3)) Then
            Return False
        End If
        ' e.g c:\foo
        If ((path(1) = Microsoft.VisualBasic.ChrW(58)) _
                    AndAlso IsDirectorySeparatorChar(path(2))) Then
            Return True
        End If
        ' e.g \\server\share\foo or //server/share/foo
        Return IsUncSharePath(path)
    End Function

    Private Shared Function IsDirectorySeparatorChar(ByVal ch As Char) As Boolean
        Return ((ch = Microsoft.VisualBasic.ChrW(92)) _
                    OrElse (ch = Microsoft.VisualBasic.ChrW(47)))
    End Function

    Friend Shared Function IsUncSharePath(ByVal path As String) As Boolean
        ' e.g \\server\share\foo or //server/share/foo
        If ((path.Length > 2) _
                    AndAlso (IsDirectorySeparatorChar(path(0)) AndAlso IsDirectorySeparatorChar(path(1)))) Then
            Return True
        End If
        Return False
    End Function
End Class
